Amber's Digital Garden

Texel Anti Aliasing and Lighting Godot

A simple shader for doing per-Texel lighting and anti-aliasing in Godot.


shader_type spatial;

uniform sampler2D t: source_color;

varying flat vec3 normal;

// https://www.youtube.com/watch?v=d6tp43wZqps
// Texel ati-aliasing
vec2 texelAntiAliasingUVs(sampler2D tex, vec2 base_uv){
    vec2 texture_size = vec2(textureSize(tex, 0));
    vec2 box_size = clamp(fwidth(base_uv) * texture_size, 1e-5, 1.0);
    vec2 texel = base_uv * texture_size - vec2(0.5) * box_size;
    vec2 texel_offset = smoothstep(vec2(1.0) - box_size, vec2(1.0), vec2(fract(texel.x), fract(texel.y)));
    vec2 uv = (floor(texel) + vec2(0.5) + texel_offset) * (1.0 / texture_size);
    
    return uv;
}

// https://www.youtube.com/watch?v=2giKvEC-A3o
// https://discussions.unity.com/t/the-quest-for-efficient-per-texel-lighting/700574/20
vec4 texel_snap(vec4 value, vec2 base_uv, sampler2D tex)
{
    vec2 texture_size = vec2(textureSize(t, 0));
    vec2 texel_size = 1.0f / texture_size;
    
    vec2 duvdx = dFdx(base_uv);
    vec2 duvdy = dFdy(base_uv);
    
    vec4 dvdx = dFdx(value);
    vec4 dvdy = dFdy(value);
    
    vec2 uv_center = (floor(base_uv * texture_size) / texture_size) + (texel_size * 0.5f);
    //vec2 uv_center = texelAntiAliasingUVs(t, base_uv);
    vec2 duv = uv_center - base_uv; // distance to texel center.
    
    return value + (dvdx * (duv.x * duvdy.y - duv.y * duvdy.x) + dvdy * (duv.y * duvdx.x - duv.x * duvdx.y)) / (duvdx.x * duvdy.y - duvdy.x * duvdx.y);
}

void fragment() {
    normal = texel_snap(vec4(NORMAL, 0.0), UV, t).xyz;
    LIGHT_VERTEX = texel_snap(vec4(LIGHT_VERTEX, 0.0f), UV, t).xyz;
}

void light() {
    DIFFUSE_LIGHT += clamp(dot(normal, LIGHT), 0.0, 1.0) * ATTENUATION * LIGHT_COLOR / PI;
}

Alt fragment

Slightly more optimized fragment shader with derivatives.

void fragment() {
    vec2 texture_size = vec2(textureSize(color, 0));
    vec2 box_size = clamp(fwidthFine(UV) * texture_size, 1e-5, 1.0);
    vec2 texel = UV * texture_size - vec2(0.5) * box_size;
    vec2 texel_offset = smoothstep(vec2(1.0) - box_size, vec2(1.0), vec2(fract(texel.x), fract(texel.y)));
    vec2 uv = (floor(texel) + vec2(0.5) + texel_offset) * (1.0 / texture_size);
    
    ALBEDO = textureGrad(color, uv, dFdxFine(UV), dFdyFine(UV)).rgb;
}

Sources:


by amber